package com.finance.cooperate.dao.support.query;

import lombok.Data;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.CollectionUtils;
import javax.persistence.criteria.*;
import java.util.*;

/**
 * 类名称 : QueryParams
 * 功能描述 :封装查询条件的实体
 * 创建时间 : 2018/11/14 15:18
 * 创建人： 沈安心
 * 修改人 修改日期 修改描述：
 * -----------------------------------
 */
@Data
@SuppressWarnings("all")
public class QueryParams<T> implements Specification<T> {

    //属性分隔符
    private static final String PROPERTY_SEPARATOR = ".";
    //and条件
    private List<Filter> andFilters = new ArrayList<>();
    //or条件
    private List<Filter> orFilters = new ArrayList<>();
    //排序属性
    private List<Order> orders = new ArrayList<>();

    /*
     * 创建人：沈安心
     * 功能描述：添加一个and条件
     * 创建时间：2018/11/14 17:13
     * 入参：[filter 该条件]
     * 返回值：com.mq.common.support.jpa.specification.QueryParams 链式调用
     */
    public QueryParams and(Filter filter) {
        this.andFilters.add(filter);
        return this;
    }

    /*
     * 创建人：沈安心
     * 功能描述：添加多个and条件
     * 创建时间：2018/11/14 17:14
     * 入参：[filter 该条件]
     * 返回值：com.mq.common.support.jpa.specification.QueryParams 链式调用
     */
    public QueryParams and(Filter... filter) {
        this.andFilters.addAll(Arrays.asList(filter));
        return this;
    }

    /*
     * 创建人：沈安心
     * 功能描述：添加一个or条件
     * 创建时间：2018/11/14 17:14
     * 入参：[filter 该条件]
     * 返回值：com.mq.common.support.jpa.specification.QueryParams 链式调用
     */
    public QueryParams or(Filter filter) {
        this.orFilters.add(filter);
        return this;
    }

    /*
     * 创建人：沈安心
     * 功能描述：添加多个or条件
     * 创建时间：2018/11/14 17:15
     * 入参：[filter 该条件]
     * 返回值：com.mq.common.support.jpa.specification.QueryParams 链式调用
     */
    public QueryParams or(Filter... filter) {
        this.orFilters.addAll(Arrays.asList(filter));
        return this;
    }

    /*
     * 创建人：沈安心
     * 功能描述：升序字段
     * 创建时间：2018/11/14 17:15
     * 入参：[property 该字段对应变量名]
     * 返回值：com.mq.common.support.jpa.specification.QueryParams 链式调用
     */
    public QueryParams orderASC(String property) {
        this.orders.add(Order.asc(property));
        return this;
    }

    /*
     * 创建人：沈安心
     * 功能描述：降序字段
     * 创建时间：2018/11/14 17:15
     * 入参：[property 该字段对应变量名]
     * 返回值：com.mq.common.support.jpa.specification.QueryParams 链式调用
     */
    public QueryParams orderDESC(String property) {
        this.orders.add(Order.desc(property));
        return this;
    }

    /*
     * 创建人：沈安心
     * 功能描述：清除所有条件
     * 创建时间：2018/11/14 17:16
     * 入参：[]
     * 返回值：com.mq.common.support.jpa.specification.QueryParams 该实例
     */
    public QueryParams clearAll() {
        if (!this.andFilters.isEmpty()) this.andFilters.clear();
        if (!this.orFilters.isEmpty()) this.orFilters.clear();
        if (!this.orders.isEmpty()) this.orders.clear();
        return this;
    }

    /*
     * 创建人：沈安心
     * 功能描述：清除and条件
     * 创建时间：2018/11/14 17:16
     * 入参：[]
     * 返回值：com.mq.common.support.jpa.specification.QueryParams 该实例
     */
    public QueryParams clearAnd() {
        if (!this.andFilters.isEmpty()) this.andFilters.clear();
        return this;
    }

    /*
     * 创建人：沈安心
     * 功能描述：清除or条件
     * 创建时间：2018/11/14 17:17
     * 入参：[]
     * 返回值：com.mq.common.support.jpa.specification.QueryParams 该实例
     */
    public QueryParams clearOr() {
        if (!this.orFilters.isEmpty()) this.andFilters.clear();
        return this;
    }

    /*
     * 创建人：沈安心
     * 功能描述：清除order条件
     * 创建时间：2018/11/14 17:17
     * 入参：[]
     * 返回值：com.mq.common.support.jpa.specification.QueryParams 该实例
     */
    public QueryParams clearOrder() {
        if (!this.orders.isEmpty()) this.orders.clear();
        return this;
    }

    /*
     * 创建人：沈安心
     * 功能描述：生成条件的
     * 创建时间：2018/11/14 17:18
     * 入参：[root 该对象的封装, query 查询构建器, cb 构建器]
     * 返回值：javax.persistence.criteria.Predicate 条件集合
     */
    @Override
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        Predicate restrictions = null;
        if (andFilters.size() > 0) {
            restrictions = cb.and(toAndPredicate(root, cb));
        }
        //如果无or条件,toOrPredicate会自带一个 or 1=1,前面用and的话则变为and 1=1造成错误,所以这里判断下
        //这个貌似和版本有关
        if (orFilters.size() > 0) {
            restrictions = cb.and(restrictions, toOrPredicate(root, cb));
        }
        query.orderBy(toOrders(root, cb));
        return restrictions;
    }

    /*
     * 创建人：沈安心
     * 功能描述：获取Path
     * 创建时间：2018/11/14 17:19
     * 入参：[path, propertyPath 属性路径]
     * 返回值：javax.persistence.criteria.Path<X>
     */
    private <X> Path<X> getPath(Path<?> path, String propertyPath) {
        if (path == null || StringUtils.isEmpty(propertyPath)) {
            return (Path<X>) path;
        }
        String property = StringUtils.substringBefore(propertyPath, PROPERTY_SEPARATOR);
        return getPath(path.get(property), StringUtils.substringAfter(propertyPath, PROPERTY_SEPARATOR));
    }

    /*
     * 创建人：沈安心
     * 功能描述：转换为Predicate
     * 创建时间：2018/11/14 17:20
     * 入参：[root, criteriaBuilder]
     * 返回值：javax.persistence.criteria.Predicate
     */
    private Predicate toAndPredicate(Root<T> root, CriteriaBuilder criteriaBuilder) {
        Predicate restrictions = criteriaBuilder.conjunction();
        if (root == null || CollectionUtils.isEmpty(andFilters)) {
            return restrictions;
        }
        for (Filter filter : andFilters) {
            if (filter == null) {
                continue;
            }
            String property = filter.getProperty();
            Operator operator = filter.getOperator();
            Object[] values = filter.getValues();

            //如果设置的是动态SQL，这里就把空值条件过滤掉
            if (filter.getDynamicSql() && this.isFilter(values)) {
                continue;
            }

            Boolean ignoreCase = filter.getIgnoreCase();
            Path<?> path = getPath(root, property);
            if (path == null) {
                continue;
            }
            switch (operator) {
                case eq:
                    if (values != null) {
                        if (BooleanUtils.isTrue(ignoreCase) && String.class.isAssignableFrom(path.getJavaType()) && values[0] instanceof String) {
                            restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.equal(criteriaBuilder.lower((Path<String>) path), ((String) values[0]).toLowerCase()));
                        } else {
                            restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.equal(path, values[0]));
                        }
                    } else {
                        restrictions = criteriaBuilder.and(restrictions, path.isNull());
                    }
                    break;
                case ne:
                    if (values != null) {
                        if (BooleanUtils.isTrue(ignoreCase) && String.class.isAssignableFrom(path.getJavaType()) && values[0] instanceof String) {
                            restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.notEqual(criteriaBuilder.lower((Path<String>) path), ((String) values[0]).toLowerCase()));
                        } else {
                            restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.notEqual(path, values[0]));
                        }
                    } else {
                        restrictions = criteriaBuilder.and(restrictions, path.isNotNull());
                    }
                    break;
                case gt:
                    if (Number.class.isAssignableFrom(path.getJavaType()) || values[0] instanceof Number) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.gt((Path<Number>) path, (Number) values[0]));
                    }
                    if (Date.class.isAssignableFrom(path.getJavaType()) && values[0] instanceof Date) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.greaterThan((Path<Date>) path, (Date) values[0]));
                    }
                    break;
                case lt:

                    if (Number.class.isAssignableFrom(path.getJavaType()) || values[0] instanceof Number) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.lt((Path<Number>) path, (Number) values[0]));
                    }
                    if (Date.class.isAssignableFrom(path.getJavaType()) && values[0] instanceof Date) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.lessThan((Path<Date>) path, (Date) values[0]));
                    }
                    break;
                case ge:
                    if (Number.class.isAssignableFrom(path.getJavaType()) || values[0] instanceof Number) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.ge((Path<Number>) path, (Number) values[0]));
                    }
                    if (Date.class.isAssignableFrom(path.getJavaType()) && values[0] instanceof Date) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.greaterThanOrEqualTo((Path<Date>) path, (Date) values[0]));
                    }
                    break;
                case le:
                    if (Number.class.isAssignableFrom(path.getJavaType()) || values[0] instanceof Number) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.le((Path<Number>) path, (Number) values[0]));
                    }
                    if (Date.class.isAssignableFrom(path.getJavaType()) && values[0] instanceof Date) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.lessThan((Path<Date>) path, (Date) values[0]));
                    }
                    break;
                case like:
                    if (String.class.isAssignableFrom(path.getJavaType()) && values[0] instanceof String && !"".equals(values[0])) {
                        if (BooleanUtils.isTrue(ignoreCase)) {
                            restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.like(criteriaBuilder.lower((Path<String>) path), "%" + ((String) values[0]).toLowerCase() + "%"));
                        } else {
                            restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.like((Path<String>) path, "%" + values[0] + "%"));
                        }
                    }
                    break;
                case likeL:
                    if (String.class.isAssignableFrom(path.getJavaType()) && values[0] instanceof String && !"".equals(values[0])) {
                        if (BooleanUtils.isTrue(ignoreCase)) {
                            restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.like(criteriaBuilder.lower((Path<String>) path), "%" + ((String) values[0]).toLowerCase()));
                        } else {
                            restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.like((Path<String>) path, "%" + values[0]));
                        }
                    }
                    break;
                case likeR:
                    if (String.class.isAssignableFrom(path.getJavaType()) && values[0] instanceof String && !"".equals(values[0])) {
                        if (BooleanUtils.isTrue(ignoreCase)) {
                            restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.like(criteriaBuilder.lower((Path<String>) path), ((String) values[0]).toLowerCase() + "%"));
                        } else {
                            restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.like((Path<String>) path, values[0] + "%"));
                        }
                    }
                    break;
                case in:
                    if (values[0] instanceof Collection) {
                        restrictions = criteriaBuilder.and(restrictions, path.in((Collection) values[0]));
                    }
                    //restrictions = criteriaBuilder.and(restrictions, path.in(values[0]));
                    break;
                case isNull:
                    restrictions = criteriaBuilder.and(restrictions, path.isNull());
                    break;
                case between:
                    if (Number.class.isAssignableFrom(path.getJavaType())) {
                        if (values[0] != null && values[0] instanceof Number) {
                            restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.ge((Path<Number>) path, (Number) values[0]));
                        }
                        if (values[1] != null && values[1] instanceof Number) {
                            restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.lt((Path<Number>) path, (Number) values[1]));
                        }

                    }
                    if (Date.class.isAssignableFrom(path.getJavaType())) {
                        if (values[0] != null && values[0] instanceof Date) {
                            restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.greaterThanOrEqualTo((Path<Date>) path, (Date) values[0]));
                        }
                        if (values[1] != null && values[1] instanceof Date) {
                            restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.lessThan((Path<Date>) path, (Date) values[1]));
                        }
                    }
                    break;
                case isNotNull:
                    restrictions = criteriaBuilder.and(restrictions, path.isNotNull());
                    break;
            }
        }
        return restrictions;
    }

    /*
     * 创建人：沈安心
     * 功能描述：转换为Predicate
     * 创建时间：2018/11/14 17:21
     * 入参：[root, criteriaBuilder]
     * 返回值：javax.persistence.criteria.Predicate
     */
    private Predicate toOrPredicate(Root<T> root, CriteriaBuilder criteriaBuilder) {
        Predicate restrictions = criteriaBuilder.disjunction();
        if (root == null || CollectionUtils.isEmpty(andFilters)) {
            return restrictions;
        }
        for (Filter filter : orFilters) {
            if (filter == null) {
                continue;
            }
            String property = filter.getProperty();
            Operator operator = filter.getOperator();
            Object[] values = filter.getValues();


            //如果设置的是动态SQL，这里就把空值条件过滤掉
            if (filter.getDynamicSql() && this.isFilter(values)) {
                continue;
            }


            Boolean ignoreCase = filter.getIgnoreCase();
            Path<?> path = getPath(root, property);
            if (path == null) {
                continue;
            }
            switch (operator) {
                case eq:
                    if (values != null) {
                        if (BooleanUtils.isTrue(ignoreCase) && String.class.isAssignableFrom(path.getJavaType()) && values[0] instanceof String) {
                            restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.equal(criteriaBuilder.lower((Path<String>) path), ((String) values[0]).toLowerCase()));
                        } else {
                            restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.equal(path, values[0]));
                        }
                    } else {
                        restrictions = criteriaBuilder.or(restrictions, path.isNull());
                    }
                    break;
                case ne:
                    if (values != null) {
                        if (BooleanUtils.isTrue(ignoreCase) && String.class.isAssignableFrom(path.getJavaType()) && values[0] instanceof String) {
                            restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.notEqual(criteriaBuilder.lower((Path<String>) path), ((String) values[0]).toLowerCase()));
                        } else {
                            restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.notEqual(path, values[0]));
                        }
                    } else {
                        restrictions = criteriaBuilder.or(restrictions, path.isNotNull());
                    }
                    break;
                case gt:
                    if (Number.class.isAssignableFrom(path.getJavaType()) || values[0] instanceof Number) {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.gt((Path<Number>) path, (Number) values[0]));
                    }
                    if (Date.class.isAssignableFrom(path.getJavaType()) && values[0] instanceof Date) {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.greaterThan((Path<Date>) path, (Date) values[0]));
                    }
                    break;
                case lt:
                    if (Number.class.isAssignableFrom(path.getJavaType()) || values[0] instanceof Number) {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.lt((Path<Number>) path, (Number) values[0]));
                    }
                    if (Date.class.isAssignableFrom(path.getJavaType()) && values[0] instanceof Date) {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.lessThan((Path<Date>) path, (Date) values[0]));
                    }
                    break;
                case ge:
                    if (Number.class.isAssignableFrom(path.getJavaType()) || values[0] instanceof Number) {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.ge((Path<Number>) path, (Number) values[0]));
                    }
                    if (Date.class.isAssignableFrom(path.getJavaType()) && values[0] instanceof Date) {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.greaterThanOrEqualTo((Path<Date>) path, (Date) values[0]));
                    }
                    break;
                case le:
                    if (Number.class.isAssignableFrom(path.getJavaType()) || values[0] instanceof Number) {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.le((Path<Number>) path, (Number) values[0]));
                    }
                    if (Date.class.isAssignableFrom(path.getJavaType()) && values[0] instanceof Date) {
                        restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.lessThanOrEqualTo((Path<Date>) path, (Date) values[0]));
                    }
                    break;
                case like:
                    if (String.class.isAssignableFrom(path.getJavaType()) && values[0] instanceof String) {
                        if (BooleanUtils.isTrue(ignoreCase)) {
                            restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.like(criteriaBuilder.lower((Path<String>) path), ((String) values[0]).toLowerCase()));
                        } else {
                            restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.like((Path<String>) path, (String) values[0]));
                        }
                    }
                    break;
                case likeL:
                    if (String.class.isAssignableFrom(path.getJavaType()) && values[0] instanceof String) {
                        if (BooleanUtils.isTrue(ignoreCase)) {
                            restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.like(criteriaBuilder.lower((Path<String>) path), "%" + ((String) values[0]).toLowerCase()));
                        } else {
                            restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.like((Path<String>) path, "%" + values[0]));
                        }
                    }
                    break;
                case likeR:
                    if (String.class.isAssignableFrom(path.getJavaType()) && values[0] instanceof String) {
                        if (BooleanUtils.isTrue(ignoreCase)) {
                            restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.like(criteriaBuilder.lower((Path<String>) path), ((String) values[0]).toLowerCase() + "%"));
                        } else {
                            restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.like((Path<String>) path, values[0] + "%"));
                        }
                    }
                    break;
                case in:
                    if (values[0] instanceof Collection) {
                        restrictions = criteriaBuilder.or(restrictions, path.in((Collection) values[0]));
                    }
                    //restrictions = criteriaBuilder.or(restrictions, path.in(values[0]));
                    break;
                case between:
                    if (Number.class.isAssignableFrom(path.getJavaType())) {
                        if (values[0] != null && values[0] instanceof Number) {
                            restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.ge((Path<Number>) path, (Number) values[0]));
                        }
                        if (values[1] != null && values[1] instanceof Number) {
                            restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.lt((Path<Number>) path, (Number) values[1]));
                        }
                    }
                    if (Date.class.isAssignableFrom(path.getJavaType())) {
                        if (values[0] != null && values[0] instanceof Date) {
                            restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.greaterThanOrEqualTo((Path<Date>) path, (Date) values[0]));
                        }
                        if (values[1] != null && values[1] instanceof Date) {
                            restrictions = criteriaBuilder.or(restrictions, criteriaBuilder.lessThan((Path<Date>) path, (Date) values[1]));
                        }
                    }
                    break;
                case isNull:
                    restrictions = criteriaBuilder.or(restrictions, path.isNull());
                    break;
                case isNotNull:
                    restrictions = criteriaBuilder.or(restrictions, path.isNotNull());
                    break;
            }
        }
        return restrictions;
    }

    /*
     * 创建人：沈安心
     * 功能描述：转换为Order
     * 创建时间：2018/11/14 17:21
     * 入参：[root, criteriaBuilder]
     * 返回值：java.util.List<javax.persistence.criteria.Order>
     */
    private List<javax.persistence.criteria.Order> toOrders(Root<T> root, CriteriaBuilder criteriaBuilder) {
        List<javax.persistence.criteria.Order> orderList = new ArrayList<javax.persistence.criteria.Order>();
        if (root == null || CollectionUtils.isEmpty(orders)) {
            return orderList;
        }
        for (Order order : orders) {
            if (order == null) {
                continue;
            }
            String property = order.getProperty();
            Direction direction = order.getDirection();
            Path<?> path = getPath(root, property);
            if (path == null || direction == null) {
                continue;
            }
            switch (direction) {
                case asc:
                    orderList.add(criteriaBuilder.asc(path));
                    break;
                case desc:
                    orderList.add(criteriaBuilder.desc(path));
                    break;
            }
        }
        return orderList;
    }

    /*
     * 创建人：沈安心
     * 功能描述：根据值是否为空，来判断是否过滤该条件
     * 创建时间：2018/12/27 17:30
     * 入参：[values]
     * 返回值：boolean
     */
    private boolean isFilter(Object[] values) {
        Boolean result = false;
        if (values != null && values.length > 0) {
            Boolean flag = false;
            for (Object value : values) {
                if (value != null && !"null".equals(value)) {
                    if (value.getClass().isAssignableFrom(String.class)) {
                        if (!String.valueOf(value).trim().equals("")) {
                            flag = true;
                        }
                    } else {
                        flag = true;
                    }
                }
            }
            if (!flag) {
                result = true;
            }
        } else {
            result = true;
        }

        return result;
    }

}
